Skip to content

SVG 里都有什么

Author: [麟十一]

Link: [https://mp.weixin.qq.com/s/KE2arh0Me0JhaBYnbsvWCA]

最近我一直在研究有关矢量图和位图的内容,目前工作已经基本收尾,趁着这个部分还比较熟悉,先写一篇记录一下。 这一篇文章包括三部分,第一部分是矢量图和位图的定义,第二部分展示 SVG 文件的格式,第三部分详细介绍一下 path 路径中的一些命令。下一篇文章是配套教程--Python 如何解析 SVG 文件&将 SVG 文件转换为 PNG 文件。因为 SVG 里面的内容真的太多了,本篇只涵盖我研究过的部分,其他部分感兴趣的话在文中的链接内自行查看哦

1. 矢量图和位图

有关矢量图和位图的定义感觉维基百科上面说地更清楚一些。先看矢量图像(Vector Graphics) 的定义:

"Vector graphics are computer graphics images that are defined in terms of points on a Cartesian plane, which are connected by lines and curves to form polygons and other shapes. " 位图(Bitmap) 或者 栅格图像(Raster Graphics) 的定义为: "A raster graphic or bitmap image is a dot matrix data structure that represents a generally rectangular grid of pixels (points of color)."

矢量图像由有方向的点、曲线和直线构成各种形状,这些点和线的方向决定了图形路径的走向。而位图是一种点阵数据结构,位图也可以叫做点阵图 ,每一张图片都是由像素构成的。一句话总结,矢量图像基于“形状”,位图图像基于“像素” 。 我们可以随意挑选一张位图图片检查图像属性,可以发现图片的宽和高都是以像素为单位的,从下图可以看到,该图片宽和高都是 320 个像素,说明该图片是由 320*320=102400 个像素点构成的。 图片属性界面

另外,矢量图相较于位图最大的优点是图像的缩小和放大不会影响分辨率 。举个例子,下图是一张汉字“一.png”的位图图片: 汉字“一.png”图片 如果将图像放大,可以发现汉字的轮廓是由灰度不同的小方格组成,这些小方格就是形成汉字轮廓的像素点: 汉字“一.png”图片放大 而将“一.svg”图片放大之后就不会有这个问题,汉字轮廓依旧非常清晰,因为矢量图像是不会失真的,拿这张 SVG 图片来说,不论图像如何变化,汉字轮廓始终都由大小和宽度不会改变的点和线构成: 汉字“一.svg”图片放大

总结一下矢量图和位图的区别:

1.1 图像构成

矢量图基于点和线(或者形状);位图基于像素

1.2 分辨率

矢量图没有分辨率的概念,图像的放大或缩小不会影响清晰度;位图的清晰度由分辨率决定,分辨率越大,图像越清晰,图像放大会损失清晰度,俗称图像失真

1.3 相互转换

矢量图转换为位图比较容易,使用 Python 就可以实现(下一篇会详细说明);位图转换为矢量图会更加复杂

1.4 图像色彩

矢量图的色彩比较单一,一般用来设计 Logo 这类不需要太复杂色彩的图片;位图中可选择的色彩很丰富,每一个像素点都可以拥有 RGB 三通道颜色,所以位图可以显示出非常逼真的图像 ### 1.5 文件大小 矢量图文件的大小与图形复杂度有关,所占空间较小;由于位图的色彩比较丰富,像素点包含的信息很多,文件所占空间较大

1.6 文件格式

矢量图的常见格式有.svg(Scalable Vector Graphics) .eps(Encapsulated PostScript) .ai(Adobe Illustrator document)等;位图的常见格式有.png(Portable Network Graphic) .bmp(Bitmap Image File) .jpg/.jpeg(Joint Photographic Experts Group) .gif(Graphics Interchange Format)等;另外,.pdf(Portable Document Format)既可以是矢量图,也可以是位图

2. 有关 SVG

接下来主要谈一谈 SVG 类型的图片,SVG(Scalable Vector Graphics)全称是可伸缩矢量图形 ,贴一个官网链接:https://svgwg.org/,里面内容非常多也非常全面,只不过界面有一些简约: SVG 官网界面

在官网中找到的 SVG 定义的整合+修改版为:

SVG is an XML language, similar to XHTML, which can be used to describe two-dimensional graphics. SVG allows for three types of graphic objects: vector graphic shapes (e.g., paths consisting of straight lines and curves), images and text.

简单来说 SVG 就是一种可以来画二维矢量图的 XML 语言。它由直线、曲线这些矢量形状、图片和文本共三类图形对象构成。 emmm,那么什么是 XML 语言?学过 HTML 的同学肯定会很熟悉,XML(EXtensible Markup Language) 指可扩展标记语言 ,它用于标记电子文件而使其具有结构性,XML 常被用来标记、传输和存储数据。如果没有学过 HTML 的话,那么 JSON 格式的数据大家一定不陌生,JSON 和 XML 属于目前常用的两种数据交换格式,或者说互联网中存储和传输过程中使用的数据格式。(有关 JSON 数据的读取貌似在后面我也会写一篇文,我想写的也太多了)

为了直观地感受 SVG 文件格式,先举一个很简单的例子:

html
<svg version="1.1" height="400" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <title>Example</title>
  <circle cx="20" cy="20" r="20" fill="lightblue"></circle>
  <rect x="20" y="20" width="50" height="70" fill="none" stroke="green"></rect>
  <path d="M 50 50 L 150 50 L 100 150 Z"  fill="pink" stroke="red" stroke-width="3" ></path>
</svg>

将代码复制到文本文件里然后保存为 SVG 格式,打开后在网页端就能看到我们绘制出的图形:

Example

从上文代码中就能看出来什么叫做“XML 语言使得电子文件具有结构性”。SVG 代码由一个一个标签构成,每一个标签包含不同的属性,每一个标签都必须有开启标签和关闭标签。代码总共有 6 行,包含了 5 个标签。接下来我们一个一个进行解析:

标签一: < svg version="1.1" width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"></svg> SVG 代码以 <svg> 标签开始,<svg>标签也可以被认为是根标签或者根元素。标签中包括 version,width,height, viewBoxxmlns 共 5 个属性。分别代表 SVG 版本,画布宽度,高度,可视窗口大小和 SVG 命名空间。属性 widthheight 默认单位是 px(像素 ,也可以设置成 cm(厘米),只不过要注意二者之间的转换。另外,widthheight 属性可以用百分比来设置,如果设置为 100%,代表图片会填满 viewBox 设置的可视窗口。 在这里需要说明一下 viewBox 这个属性,代码中设置的是 0 0 200 200,代表可视窗口从左上角坐标(0,0)开始,一直到右下角(200,200)。如果把可视窗口设置为 0 0 100 100,就会发现图形只显示了从左上角(0,0)到右下角(100,100)的部分: 修改 viewBox 后绘制出的图形

标签二:<title>Example</title> 比较简单,标签< title>定义了一个描述性的字符串,这个字符串只能是纯文本。这个 title 会显示在网页的标题上。

标签三: < circle cx="20" cy="20" r="20" fill="lightblue" stroke="none"></circle> 绘制了一个圆,<circle>标签属性有 cx,cy,r,和 fill。分别代表圆心的 x 坐标,y 坐标,圆的半径和填充色。如果不设置圆心坐标,则默认为左上角(0,0)。

标签四: <rect x="20" y="20" width="50" height="70" fill="none" stroke="green"></rect>绘制了一个矩形,<rect>标签包含 x,y,width,height,fill 和 stroke 6 个属性,分别代表举行左上角的 x 坐标,y 坐标,举行宽度,高度,填充色和轮廓颜色。和圆相同,如果不设置 x 和 y 坐标则默认为(0,0)。另外,fill=“none” 代表了无填充色,也可以设置为**“transparent”** 透明填充色。

标签五: <path d="M 50 50 L 150 50 L 100 150 z" fill="pink" stroke="red" stroke-width="3"></path> 则是绘制了一条路径,有关<path>标签的具体内容在后文会详细介绍。这里先介绍属性,包括 d,fill,stroke 和 stroke-width,代表路径,填充色,轮廓颜色和轮廓宽度。

我们从图中还可以发现三个形状是一个叠着一个的,类似于三个图层,后面的形状会覆盖掉前面的形状。另外,SVG 文件中还有许多的标签,<path>标签中的属性还可以画出各种各样的图形。除了在刚才提到的官网上可以找到各类标签的详细解释之外,还有一个网站也很推荐,可以选择中文版: https://developer.mozilla.org/en-US/docs/Web/SVG

3. 有关 SVG 中的 <path>

最后一部分详细介绍一下神奇的<path>标签,这个标签是 SVG 中功能最强大的一个,除了上文创建的矩形,圆形,还可以在属性 d 中绘制非常多的形状。那么 属性 d 的值是什么?简单来说,它是由 “命令+参数”的序列构成的字符串 。接下来就介绍一下属性 d 中的所有命令。

重要

提前说几个点:所有的命令都有大写和小写之分,大写表示绝对位置,小写代表相对位置 ;命令最后的参数表示最后要到达的位置;上一个命令结束的位置即为下一个命令开始的位置,下文中会用 “当前点” 代表这个位置。

3.1 moveto(M/m):

moveto 命令代表一条路径的开始 ,可以认为是在纸上画线时首先点上的一个点,之后的命令和所设置的参数会从这个点开始连接。moveto的参数是x y)+ ,代表这个起始点的 xy 坐标。参数中的“+”并不需要输入,只是代表这条路径没有结束,还会继续连接到下一个命令下一个点,所有的命令中只有关闭路径参数 Z 没有“+”

3.2 lineto(L/l):

lineto 命令表示 从当前点到参数(x,y)点 绘制一条 直线 ,它的参数为 (x y)+ 。这里的当前点代表是前一个参数设置的的坐标点。

3.3 horizontal lineto(H/h)

horizontal lineto 命令和 lineto 命令相似,不过它是从当前点到参数 x 点绘制一条水平线 ,参数是 x +

3.4 vertical lineto(V/v):

vertical lineto 命令是从当前点绘制一条垂直线 ,参数是y +

在开始下一个命令之前,先介绍一下贝塞尔曲线(Bézier curve),这是一种由两个定点和零至无数个控制点绘制的曲线,属性 d 中的命令会绘制二次贝塞尔曲线和三次贝塞尔曲线。贴一个网址: https://www.jasondavies.com/animated-bezie ,里面包括了贝塞尔曲线的动画,还有相关介绍(下图截取自该网页,从左至右依次是一次、二次、三次和四次贝塞尔曲线的动画): 贝塞尔曲线动画

3.5 quadratic Bézier curveto(Q/q):

quadratic Bézier curveto 命令表示从当前点开始,绘制一条二次贝塞尔曲线 ,绘制二次贝塞尔曲线需要三个点,一个起点,一个终点,还有一个用于绘制曲线的控制点。在这个命令中,参数是 (x1 y1 x y)+ ,当前点是曲线起点,(x1 y1)为控制点,(x y)为曲线终点。

3.6 smooth quadratic Bézier curveto(T/t):

smooth quadratic Bézier curveto 命令是 绘制光滑的二次贝塞尔曲线 ,一般来说这个命令都会跟在 quadratic Bézier curveto(Q/q)命令之后,参数只有 (x y)+,这里的 xy 代表曲线的终点,曲线的 控制点会自动取前一条曲线控制点的对称点 (相对于上一段曲线的终点)。 举一个例子,用下面的代码绘制两条曲线:

html
<svg version="1.1" viewBox="-150 -150 1000 1000" xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    <![CDATA[ .Connect { stroke:#888888; stroke-width:2 }    .SamplePath {}    .Point { fill:#888888 }]]>
  </style>
  <!--二次贝塞尔曲线-->
  <path
    class="SamplePath"
    d="M20 100 Q100 300 310 100"
    fill="none"
    stroke="red"
    stroke-width="8"
  />
  <circle class="Point" cx="20" cy="100" r="8" />
  <!--起点-->
  <circle class="Point" cx="100" cy="300" r="8" />
  <!--控制点-->
  <circle class="Point" cx="310" cy="100" r="8" />
  <!--终点-->
  <polyline class="Connect" points="20 100 100 300" />
  <polyline class="Connect" points="310 100 100 300" />
  <!--二次平滑贝塞尔曲线你-->
  <path
    class="SamplePath"
    d="M20 100 Q100 300 310 100 T500 300"
    fill="none"
    stroke="blue"
    stroke-width="3"
  />
  <circle class="Point" cx="520" cy="-100" r="8" />
  <!--控制点-->
  <circle class="Point" cx="500" cy="300" r="8" />
  <!--终点-->
  <polyline class="Connect" points="500 300 520 -100" />
  <polyline class="Connect" points="310 100 520 -100" />
</svg>

最终画出的曲线如下图所示,其中左边红色的一段是使用命令 Q 画出的二次贝塞尔曲线,我用三个点标注了起点,控制点和终点。而蓝色的长曲线是在Q 命令后又加上了 T 命令,可以发现曲线自动取了Q 命令中控制点的对称点作为新的控制点,Q 命令中的终点作为新的起点,T 命令中的终点作为终点 ,画出了两条二次贝塞尔曲线(PS:新的控制点是我自己算出来然后画出来的点): 两条二次贝塞尔曲线(一条 Q,一条 QT)

### 3.7 cubic Bézier curveto(C/c): **cubic Bézier curveto 命令代表绘制一条**三次贝塞尔曲线** 。与二次贝塞尔曲线不同的是,三次贝塞尔曲线拥有两个控制点。参数为**(x1 y1 x2 y2 x y)+\*\* ,其中(x1 y1)指的是控制点 1,(x2 y2)指控制点 2,(x y)代表曲线终点,曲线起点为当前点。

3.8 smooth cubic Bézier curveto(S/s):

smooth cubic Bézier curveto 命令绘制光滑的三次贝塞尔曲线,和光滑的二次贝塞尔曲线命令相似,该命令的参数是(x2 y2 x y)+** ,(x2 y2)为曲线第二个控制点,(x y)为曲线终点。曲线起点还是当前点,不过第一个控制点会比较复杂,它与**第一条 C 命令中的第二个控制点关于第一条曲线的终点对称** ,也就是关于曲线起点对称。 同样,使用下面的代码绘制两条三次贝塞尔曲线:

html
<svg version="1.1"  viewBox="-150 -150 1000 1000"  xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    <![CDATA[    .Connect { stroke:#888888; stroke-width:2 }    .SamplePath {}    .Point { fill:#888888 }]]>
  </style>
  <!--三次贝塞尔曲线-->
  <path
    class="SamplePath"
    d="M20 100 C100 200 300 300 400 100"
    fill="none"
    stroke="red"
    stroke-width="8"
  />
  <circle class="Point" cx="20" cy="100" r="8" />
  <!--起点-->
  <circle class="Point" cx="100" cy="200" r="8" />
  <!--控制点1-->
  <circle class="Point" cx="300" cy="300" r="8" />
  <!--控制点2-->
  <circle class="Point" cx="400" cy="100" r="8" />
  <!--终点-->
  <polyline class="Connect" points="20 100 100 200" />
  <polyline class="Connect" points="300 300 400 100" />
  <!--三次平滑贝塞尔曲线你-->
  <path
    class="SamplePath"
    d="M20 100 C100 200 300 300 400 100 S400 300 500 300"
    fill="none"
    stroke="blue"
    stroke-width="3"
  />
  <circle class="Point" cx="500" cy="-100" r="8" />
  <!--控制点1-->
  <circle class="Point" cx="400" cy="300" r="8" />
  <!--控制点2-->
  <circle class="Point" cx="500" cy="300" r="8" />
  <!--终点-->
  <polyline class="Connect" points="400 100 500 -100" />
  <polyline class="Connect" points="400 300 500 300" />
</svg>

最终画出的曲线如下图所示,左边红色的一段是使用命令 C 画出的三次贝塞尔曲线,蓝色的长曲线是在 C 命令后又加上了 S 命令画出的两条三次贝塞尔曲线曲线,曲线自动取了C 命令中控制点 2 关于曲线终点的对称点作为新的控制点 1,C 命令中的曲线终点作为新的起点

两条三次贝塞尔曲线(一条 C,一条 CS)

3.9 elliptical arc(A/a):

elliptical arc 命令可以说是最复杂的命令了,它绘制一个圆弧 。参数为(rx ry xr laf sf x y)+ ,看着就累了 这些参数为 rx(radius-x)弧线所在椭圆的 x 半轴长,ry(radius-y)弧线所在椭圆的 y 半轴长,xr(x-axis-rotaiton)弧线所在椭圆的长轴角度,laf(large-arc-flag)是否选择弧长较长的一段弧线,sf(sweep-flag)是否选择逆时针防线的那一段弧线,x 弧线终点 x 坐标,y 弧线终点 y 坐标。 最难的命令要举最好看的例子,弧线虽然难画,但是可以实现各种曲线,在这里我画了一个桃心,还加入了另一个超有用的标签<animate>显示动画,前方彩色爱心出没:

这是实现代码:其实还挺简单的是不是

html
<svg version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path
    d="M 10 30 A 20 20 0 0 1 50 30 A 20 20 0 0 1 90 30 Q 90 60 50 90 Q 10 60 10 30 z"
    fill="red"
  >
    <animate
      attributeName="fill"
      values="red; orange; yellow; green; pink"
      dur="2s"
      repeatCount="indefinite"
    >
    </animate>

  </path>
</svg>

3.10 closepath(Z/z):

closepath 命令表示 路径结束 ,这个命令 不需要输入参数,它会自动连接路径的开始点,也就是 moveto(M/m)命令设置的坐标点,关闭整条路径 。

写在最后 其实,这一篇文原本是打算作为下一篇“Python 解析 SVG 文件”的前言部分,我之前打算写的也是有关 Python 解析的一些内容,但当时觉得把一些简介内容先讲一讲会更好,所以才开始写这一篇。原打算简单写写,结果越写越多,这一篇 6000 个字累了 虽然看上去比较长,其实这一篇也只是讲了 SVG 里面非常非常小的一部分。SVG 官方文档真的看到眼冒金星,只想感叹懂得还是太少了。当时刚接触 SVG 的时候更是一团乱麻,不知从何下手,不过慢慢地还是整理得稍微清楚了一点,继续学习吧,顺便抓紧把下一篇写了。我觉得我攒的内容根本写不完了

~END~